﻿#region 方法
// 方法
// 访问修饰符 返回值 方法名 (参数)
// {
//      方法主体
// }

// 访问修饰符 public 公用的 protected 保护的 private 私有的
// 可省略 如果省略 就是用默认值 private

// 返回值 方法返回的数据类型
// 非空类型必须有return; 且返回的类型需要匹配

// 方法名 大小写敏感 不能使用关键字 数字开头 特殊符号
// 只能以字母或下划线开头 （约定俗成的）首字母大写

// 参数列表 用()括起来 多个用,隔开
// 类型 参数名
// 参数的默认值需要从右往左给 因为默认值可以省略

// 方法的调用 方法名(参数列表);
// 参数列表数量需要一一对应，有默认值的可以省略


//int _Test(int a, int b, int c=0)
//{
//    return 1;
//}

//_Test(1, 1);
#endregion


// 游戏循环框架
// 游戏本质上就是一个死循环
// 初始化阶段 Init
// 更新阶段 Update
// 渲染（显示）阶段 Render
// 结束阶段 End
// 原则 专门的事放在专门的方法内


using static System.Runtime.InteropServices.JavaScript.JSType;

bool isOver = false;
int fps = 60;
// 获取时间
// 1 s = 1000 ms = 1000 000 us = 1000 000 000 ns = 1000 000 000 000 ps
// 1 Tick = 100 ns
// 1 s = 10 000 000 Tick
long currentTime, lastTime;
ConsoleKeyInfo key;
Pos pos;
Dir dir;
string fileName = "../../../map.csv";
int[,] map;
int boxCount;
Scene scene;
Menu selectMenue;
MenuCtrl menuCtrl;
Pos[] footStep;
int footCount;
bool BackFlag = false;
int[,,] mapStep;

// 记录足迹
void RecordFootStep()
{
    // 回退时不记录
    if (BackFlag) return;

    if (footCount > footStep.Length - 1)   
    {
        footCount = footStep.Length - 1;
        for (int i = 0; i < footStep.Length-1; i++)
        {
            footStep[i] = footStep[i + 1];
            for (int j = 0; j < 30; j++)
            {
                for (int k = 0;k < 60; k++)
                {
                    mapStep[i, j, k] = mapStep[i + 1, j, k];
                }
            }

        }
    }
    // 记录足迹
    footStep[footCount] = pos;
    // 记录地图
    CopyMap(footCount);

    footCount++;
}

// 读取足迹
void LoadFoodStep()
{
    BackFlag = true;
    if (footCount < 1)
        footCount = 1;
    footCount--;
    // 读取足迹
    pos = footStep[footCount];
    // 读取地图
    //map = mapStep[footCount];
    CopyMap(footCount,false);
}

void CopyMap(int index, bool b = true)
{
    for (int i = 0;i < 30;i++)
    {
        for (int j = 0;j < 60;j++)
        {
            if (b)
            {
                mapStep[index, i, j] = map[i, j];
            }
            else
            {
                map[i, j] = mapStep[index, i, j];
            }
        }
    }
}

// 输入
void Input()
{
    key = Console.ReadKey(true);  // 参数控制是否显示字符
    switch (scene)
    {
        case Scene.Menu:
            switch (key.Key)
            {
                case ConsoleKey.W:
                    menuCtrl = MenuCtrl.Up;
                    break;
                case ConsoleKey.S:
                    menuCtrl = MenuCtrl.Down;
                    break;
                case ConsoleKey.Enter:
                    menuCtrl = MenuCtrl.Select;
                    break;
            }
            break;
        case Scene.Scene1:
            dir = Dir.None;
            switch (key.Key)
            {
                case ConsoleKey.UpArrow:
                case ConsoleKey.W:
                    dir = Dir.Up;
                    break;
                case ConsoleKey.DownArrow:
                case ConsoleKey.S:
                    dir = Dir.Down;
                    break;
                case ConsoleKey.LeftArrow:
                case ConsoleKey.A:
                    dir = Dir.Left;
                    break;
                case ConsoleKey.RightArrow:
                case ConsoleKey.D:
                    dir = Dir.Right;
                    break;
                case ConsoleKey.Q:
                    LoadFoodStep();
                    break;
                default:
                    break;
            }
            break;
        case Scene.Scene2:
            break;
        case Scene.Scene3:
            break;
        default:
            break;
    }
            // 检测按键
            // 只更改状态 不做逻辑处理
    
}

// 绘制地图
void DrawMap(int[,] data)
{
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            // 根据地图数据 绘制相应符号
            //if (data[i, j] == (int)MapType.Wall)
            //{
            //    // 如果地图数据是“墙” 那就画墙
            //    Console.SetCursorPosition(j, i); Console.Write("0");
            //}
            Console.SetCursorPosition(j, i);
            switch (data[i, j])
            {
                case (int)MapType.None:
                    Console.WriteLine(" ");
                    break;
                case (int)MapType.Wall:
                    Console.Write("X");
                    break;
                case (int)MapType.Box:
                    Console.Write("a");
                    break;
                case (int)MapType.Exit:
                    Console.Write("@");
                    break;
                case (int)MapType.Complete:
                    Console.Write("G");
                    break;
                default:
                    break;
            }
        }
    }
}


void Move()
{
    RecordFootStep();
    // 根据移动方向修改坐标
    switch (dir)
    {
        case Dir.Up:
            pos.y--;
            break;
        case Dir.Down:
            pos.y++;
            break;
        case Dir.Left:
            pos.x--;
            break;
        case Dir.Right:
            pos.x++;
            break;
    }
}

void Push(MapType nextType, MapType originType)
{
    // 箱子前进一格
    // 箱子的位置（玩家的前一格）变成空地
    // 箱子的前一格（玩家的前前一格）变成箱子

    // 因为在执行Push之前 角色已经Move了
    // 所以箱子的下一格就是现在角色所在位置的下一格
    // 箱子的原位置就是现在角色的所在位置
    ChangeMapType(GetNextPos(), (int)nextType);
    ChangeMapType(pos, (int)originType);

    //isOver = CheckOver();
}

// 获取下一格坐标
Pos GetNextPos()
{
    Pos next;
    next.x = next.y = 0;
    next.x0 = next.y0 = 0;
    switch (dir)
    {
        case Dir.Up:
            next.x = pos.x;
            next.y = pos.y - 1;
            break;
        case Dir.Down:
            next.x = pos.x;
            next.y = pos.y + 1;
            break;
        case Dir.Left:
            next.x = pos.x - 1;
            next.y = pos.y;
            break;
        case Dir.Right:
            next.x = pos.x + 1;
            next.y = pos.y;
            break;
    }
    return next;
}

// 获取下下一格坐标
Pos GetNextNextPos()
{
    Pos next;
    next.x = next.y = 0;
    next.x0 = next.y0 = 0;
    switch (dir)
    {
        case Dir.Up:
            next.x = pos.x;
            next.y = pos.y - 2;
            break;
        case Dir.Down:
            next.x = pos.x;
            next.y = pos.y + 2;
            break;
        case Dir.Left:
            next.x = pos.x - 2;
            next.y = pos.y;
            break;
        case Dir.Right:
            next.x = pos.x + 2;
            next.y = pos.y;
            break;
    }
    return next;
}

// 根据坐标获得地图数据
int GetMapType(Pos pos)
{
    return map[pos.y, pos.x];
}

// 修改地图数据
void ChangeMapType(Pos pos, int type)
{
    map[pos.y, pos.x] = type;
}


// 初始化
void Init()
{
    // 初始化窗口
    Console.CursorVisible = false;
    Console.Title = "GameLoop";
    Console.SetWindowSize(100, 30);

    // 绘制边框
    Console.Clear();

    // 初始化数据
    map = new int[30, 60];
    boxCount = 0;
    scene = Scene.Menu;
    selectMenue = Menu.Start;
    menuCtrl = MenuCtrl.None;
    footStep = new Pos[10];
    footCount = 0;
    mapStep = new int[10,30,60];

    pos.x = 10;
    pos.y = 10;
    // 读取文件
    if (File.Exists(fileName))
    {
        // 读取文件, 这里using表示只在这里读取(使用？)，出了之后释放？以免内存泄漏
        using (StreamReader sr = new StreamReader(fileName))
        {
            string data = sr.ReadToEnd();
            // 字符串分割 Split, 这里的文本中用",""换行"分离
            string[] lines = data.Split('\n'); // 分隔的也可使字符串
            for (int i = 0; i < lines.Length; i++)
            {
                string[] elements = lines[i].Split(',');
                for (int j = 0; j < elements.Length; j++)
                {
                    map[i, j] = int.Parse(elements[j]);
                    if (map[i, j] == (int)MapType.Box)
                    {
                        boxCount++;
                    }
                    if (map[i, j] == (int)MapType.Spawn)
                    {
                        map[i,j] = (int)MapType.None;
                        pos.x = j;
                        pos.y = i;
                    }
                }
            }
        };
    }

    pos.x0 = pos.x;
    pos.y0 = pos.y;
    dir = Dir.None;
    RecordFootStep();
}

// 更新
void Update()
{
    Input();
    // 逻辑

    switch (scene)
    {
        case Scene.Menu:
            switch (menuCtrl)
            {
                case MenuCtrl.Up:
                    selectMenue--;
                    if (selectMenue < Menu.Start)
                    {
                        selectMenue = Menu.Exit; 
                    }
                    break;
                case MenuCtrl.Down:
                    selectMenue++;
                    if (selectMenue > Menu.Exit)
                    {
                        selectMenue = Menu.Start;
                    }
                    break;
                case MenuCtrl.Select:
                    switch (selectMenue)
                    {
                        case Menu.Start:
                            scene = Scene.Scene1;
                            Console.Clear();
                            break;
                        case Menu.Continue:
                            //读档
                            break;
                        case Menu.Exit:
                            Environment.Exit(0);
                            break;
                    }
                    break;
            }
            break;
        case Scene.Scene1:
            if (GetMapType(GetNextPos()) == (int)MapType.None
                || GetMapType(GetNextPos()) == (int)MapType.Exit)
            {
                Move();
            }
            else if (GetMapType(GetNextPos()) == (int)MapType.Box)
            {
                if (GetMapType(GetNextNextPos()) == (int)MapType.None)
                {
                    Move();
                    Push(MapType.Box, MapType.None);
                }
                else if (GetMapType(GetNextNextPos()) == (int)MapType.Exit)
                {
                    Move();
                    Push(MapType.Complete, MapType.None);
                    boxCount--;
                    isOver = boxCount == 0;
                }
            }
            else if (GetMapType(GetNextPos()) == (int)MapType.Complete)
            {
                if (GetMapType(GetNextNextPos()) == (int)MapType.None)
                {
                    Move();
                    Push(MapType.Box, MapType.Exit);
                    boxCount++;
                }
                else if (GetMapType(GetNextNextPos()) == (int)MapType.Exit)
                {
                    Move();
                    Push(MapType.Complete, MapType.Exit);
                }
            }
            BackFlag = false;
            break;
        case Scene.Scene2:
            break;
        case Scene.Scene3:
            break;
        default:
            break;
    }

    // 如果撞到墙 就不动
    // if (没有撞到墙) Move();
    // 如何获取角色下一格的坐标？
    // 判断下一格坐标是否是墙->如何根据坐标获取地图数据
    //if (GetMapType(GetNextPos()) != (int)MapType.Wall &&
    //    GetMapType(GetNextPos()) != (int)MapType.Box)
    //{
    //    Move();
    //}
    //// 如何推动箱子
    //// 角色面前有箱子，箱子面前没有障碍物
    //// 结果 箱子前进一格，角色前进一格
    //if (pos.x == pos.x0 && pos.y == pos.y0 &&
    //    GetMapType(GetNextPos()) == (int)MapType.Box &&
    //    GetMapType(GetNextNextPos()) != (int)MapType.Box &&
    //    GetMapType(GetNextNextPos()) != (int)MapType.Wall)
    //{
    //    // 箱子前进一格
    //    // 箱子的位置（玩家的前一格）变成空地 箱子的前一格（玩家的前前一格）变成箱子
    //    ChangeMapType(GetNextPos(), (int)MapType.None);
    //    ChangeMapType(GetNextNextPos(), (int)MapType.Box);
    //    Move();
    //    Push();
    //}

    //// 限定坐标范围
    //if (map[i, j] == 0)
    //{
    //    x = x0;
    //    y = y0;
    //}
    
}

// 渲染
void Render()
{
    switch (scene)
    {
        case Scene.Menu:
            Print("游戏标题", 40, 6);
            // 如果菜单是选中状态 menu == Menu.xxx
            // 就将颜色改为红色

            Print("开始游戏", 40, 14,selectMenue == Menu.Start ? ConsoleColor.Red : ConsoleColor.White);
            Print("继续游戏", 40, 16, selectMenue == Menu.Continue ? ConsoleColor.Red : ConsoleColor.White);
            Print("结束游戏", 40, 18, selectMenue == Menu.Exit ? ConsoleColor.Red : ConsoleColor.White);
            break;
        case Scene.Scene1:
            DrawMap(map);
            // 画面
            Console.SetCursorPosition(pos.x0, pos.y0);
            Console.Write(" ");
            pos.y0 = pos.y;
            pos.x0 = pos.x;
            Console.SetCursorPosition(pos.x, pos.y); // 坐标变换 将物体坐标转换成屏幕坐标（比如绘画物体为全角，x需要*2）
                                                     // 三目运算符 二选一
                                                     // 条件 ？ 真 ： 假
                                                     // 如果角色站在目标点上 就画 2
                                                     // 如果没有就画 1
            Console.Write(GetMapType(pos) == (int)MapType.Exit ? "2" : "1");
            //Console.SetCursorPosition(30, 25);
            //Console.Write($"x:{pos.x},y:{pos.y}     x0:{pos.x0},y0:{pos.y0}");
            break;
        case Scene.Scene2:
            break;
        case Scene.Scene3: 
            break;
    }
    
}

// 检测结束
bool CheckOver()
{
    int count = 0;
    for (int i = 0; i < map.GetLength(0); i++)
    {
        for (int j = 0; j < map.GetLength(1); j++)
        {
            if (map[i,j] == (int)MapType.Exit)
            {
                count++;
            }
        }
    }
    return count == 0;
}


// 结束
void End()
{
    Console.Clear();
    Console.WriteLine("通关");
}

void Print(string str,int x,int y,ConsoleColor color = ConsoleColor.White)
{
    Console.SetCursorPosition(x, y);
    Console.ForegroundColor = color;
    Console.WriteLine(str);
    Console.ResetColor();
}

// 游戏本体
void Game()
{
    Init(); // 仅在游戏开始时执行一次
    lastTime = DateTime.Now.Ticks;
    while (!isOver)
    {
        // Input(); 按键读取可以放在这里Game()这里
        Update(); // 游戏运行时 不断的更新 逻辑相关都写在这里

        currentTime = DateTime.Now.Ticks;
        if (currentTime - lastTime > 10_000_000 / fps)
        {
            Render(); // 在更新之后 不需要每次循环都更新
            lastTime = DateTime.Now.Ticks;
        }
    }
    End(); // 释放内存
}

// 运行游戏
Game();


// 顶级语句规定自定义类型必须放在最后
// 结构体 struct 自定义的数据集合
struct Pos
{
    public int x;
    public int y;
    public int x0;
    public int y0;
};

// 枚举 enum 给数字取名字
// 更直观的了解数字的含义
enum Dir
{
    None,
    Up, 
    Down, 
    Left, 
    Right,
};

enum MapType
{
    None = 1,
    Wall = 0,
    Box = 2,
    Exit = 3,
    Complete,
    Spawn = 6
};

enum Scene
{
    Menu,
    Scene1,
    Scene2,
    Scene3,
}

enum Menu
{
    Start,
    Continue,
    Exit
}

enum MenuCtrl
{
    None,
    Up,
    Down,
    Select
}